今天來介紹 SvelteKit 中內建的表單功能,主要的特點就是我們能夠利用 HTML 的 <form>
submit 的行為來直接跟 SvelteKit 的 server-side 溝通。
首先要先知道一點是 method="POST"
的 <form>
submit 時是會直接發出一個 POST
HTTP request 且是以 content-type:"application/x-www-form-urlencoded"
的形式,所以我們在 SvelteKit 的 server-side 的程式碼就必須要用 formData()
來解析資料。
雖然現代前端應該很少直接用原生的 <form>
submit 來送出 POST
request 跟後端進行溝通,但現在這樣做的好處是我們能夠在瀏覽器「禁用 javascript」的情況下依然讓網頁有一點互動。
雖然我沒遇過,但或許某些很在意流量的場景下純 HTML 的這種設計會是還可以的解決方案吧。
首先我們要在 +page.svelte.ts
新增 actions
,這邊就可以看到我們是將把request.formData()
之後在 .get
我們要的欄位名稱。
// in src/routes/day21/+page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
console.log('day21/+page.server.ts actions.login', data.get('email'), data.get('password'));
if (!email || !password) {
return fail(400, { email, missing: true });
}
return {
status: 200,
success: true
};
}
} satisfies Actions;
然後在 +page.svelte
新增 <form>
記得設定
input
的name
以及button
type="submit"
<!-- in src/routes/day21/+page.svelte -->
<div class="flex flex-col mx-auto items-center">
<h1 class="text-primary">Login</h1>
<form method="POST" class="w-1/2">
<label class="form-control w-full">
<div class="label">
<span class="label-text">Email</span>
</div>
<input name="email" type="text" class="input input-bordered w-full" />
</label>
<label class="form-control w-full">
<div class="label">
<span class="label-text">Password</span>
</div>
<input name="password" type="password" class="input input-bordered w-full" />
</label>
<div class="flex flex-col">
<button class="btn btn-primary mt-12 w-1/2 mx-auto" type="submit">Login</button>
</div>
</form>
</div>
接下來送出表單後就可以在 terminal 中看到所送出的資料了
然後會發現我們每次送出表單時都會重整一次畫面,沒錯這就是以前前後端合一的網站會遇到的其中一個問題,這是因為每次 <form>
submit 送出 POST
request 後,通常 response 會回傳要顯示的 HTML 或者 redirect 的動作。
前後端合一也是可以用 AJAX 就是了,這裡只是指出一個比較容易遇到的場景。
那如果我們故意少填了其中一個欄位讓它故意回傳 fail
呢?
就會看到 Network 中這個 Request 被回傳 400
了
這時如果想取得在 actions
裡面 return
的值就可以利用 $page.form
將它們取出來,例如想讓使用者得知他發生錯誤了,這時就可以用以前介紹到 directives 達成如果有 $page.form?.missing
則出現 text-error
這個 class
,並且也可以使用 value={}
讓使用者輸入的 email 刷新後依然能夠留著。
<script lang="ts">
import { page } from '$app/stores';
</script>
<div class="flex flex-col mx-auto items-center">
<h1 class="text-primary">Login</h1>
<form method="POST" class="w-1/2">
<label class="form-control w-full">
<div class="label">
<span class="label-text" class:text-error={!!$page.form?.missing}>Email</span>
</div>
<input
name="email"
type="text"
class="input input-bordered w-full"
value={$page.form?.email}
/>
</label>
<label class="form-control w-full">
<div class="label">
<span class="label-text" class:text-error={!!$page.form?.missing}>Password</span>
</div>
<input name="password" type="password" class="input input-bordered w-full" />
</label>
<!-- 省略其他HTML -->
</form>
</div>
那今天如果我們想讓一個表單可以執行不同的 actions
,只要把 actions
以及 form
改為這樣
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions = {
login: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
if (!email || !password) {
return fail(400, { email, missing: true });
}
console.log('day21/+page.server.ts actions.login', data.get('email'), data.get('password'));
return {
status: 200,
success: true
};
},
register: async ({ request }) => {
const data = await request.formData();
console.log('day21/+page.server.ts actions.register', data.get('email'), data.get('password'));
}
} satisfies Actions;
<div class="flex flex-col mx-auto items-center">
<h1 class="text-primary">Login</h1>
<form method="POST" class="w-1/2" action="?/login">
<!-- 省略其他HTML -->
<div class="flex flex-col">
<button class=" btn btn-primary mt-12 w-1/2 mx-auto" type="submit">Login</button>
<button class="btn btn-secondary mt-4 w-1/2 mx-auto" type="submit" formaction="?/register">
Register
</button>
</div>
</form>
</div>
主要就是要將 form
新增 action="?/login"
來讓 button
submit 時可以會直接送出 ?/login
這個 HTTP Request ,然後新增另外一個 button
並加上formaction="?/register"
也就是讓他被點擊時是送出 ?/register
這個 HTTP Request。
講了那麼多那為什麼我們不用 onsubmit
或者 onclick
時再去打 API 就好,沒錯這就是所謂的 AJAX 而這些都是要利用 JS 才能達成的功能。
那今天這個 form
當我們關掉 JS
後它依然能正常動作
那為什麼?我們還是有用到 $page
這個 store
耶,這是因為這幾天一直有提到的一個特點因為頁面預設都會是 SSR所以 +page.svelte 在 server-side 以及 client-side 都會被執行。
所以其實我們打開 Network 看失敗的 request 就能發現其時它回傳的 HTML 已經是我們要的樣子了。
https://developer.mozilla.org/zh-TW/docs/Web/HTML/Element/form
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/submit#formaction
https://blog.kalan.dev/posts/2021-03-13-form-and-form-data
https://github.com/toddLiao469469/30days-for-svelte5/tree/main/src/routes/day21
從今天開始會將整個 repo 部署到 cloudflare pages上,如果想直接看成品的讀者可以瀏覽下方連結
https://30days-for-svelte5.pages.dev/
之後介紹到部署時也是會使用 cloudflare ,如果有興趣的讀者可以先辦個帳號來玩玩看。